package com.ouc.pampas;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.terminus.pampas.engine.config.model.Component.ComponentCategory;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ConfigManager implements Runnable {
	private static final Logger log = LoggerFactory.getLogger(ConfigManager.class);
	private final ConcurrentMap<String, FrontConfig> frontConfigMap = Maps.newConcurrentMap();
	private final ConcurrentMap<String, BackConfig> backConfigMap = Maps.newConcurrentMap();
	private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
	@Autowired
	private Setting setting;
	@Autowired(required = false)
	private AppDao appDao;
	@Autowired
	private ConfigParser configParser;

	@PostConstruct
	private void init() {
		load();
		if (this.setting.isDevMode()) {
			this.executorService.scheduleAtFixedRate(this, 5L, 5L, TimeUnit.SECONDS);
		} else {
			this.executorService.scheduleAtFixedRate(this, 5L, 5L, TimeUnit.MINUTES);
		}
	}

	public void reloadAll() {
		load();
	}

	public void reload(String appKey) {
		List<App> apps = listAllApp();
		for (App app : apps) {
			if (app.getKey().equals(appKey)) {
				loadByApp(app);
			}
		}
	}

	public List<App> listAllApp() {
		List<App> apps;
		if (this.setting.getMode() == Setting.Mode.IMPLANT) {
			apps = ImmutableList.of(this.setting.getImplantApp());
		} else {
			apps = this.appDao.listAll();
			for (App app : apps) {
				if (Strings.isNullOrEmpty(app.getAssetsHome())) {
					app.setAssetsHome(this.setting.getRootPath() + app.getKey() + "/");
				}
				if (Strings.isNullOrEmpty(app.getConfigPath())) {
					app.setConfigPath(this.setting.getRootPath() + app.getKey() + "/" + "back_config.yaml");
				}
			}
		}
		return apps;
	}

	public FrontConfig getFrontConfig(String appKey) {
		return (FrontConfig) this.frontConfigMap.get(appKey);
	}

	public FrontConfig getFrontConfig() {
		return getFrontConfig(ThreadVars.getAppKey());
	}

	public BackConfig getBackConfig(String appKey) {
		return (BackConfig) this.backConfigMap.get(appKey);
	}

	public BackConfig getBackConfig() {
		return getBackConfig(ThreadVars.getAppKey());
	}

	public Service findService(String appKey, String serviceKey) {
		BackConfig backConfig = (BackConfig) this.backConfigMap.get(appKey);
		if ((backConfig == null) || (backConfig.getServices() == null)) {
			return null;
		}
		return (Service) backConfig.getServices().get(serviceKey);
	}

	public Set<Mapping> findMappings(String appKey) {
		FrontConfig frontConfig = (FrontConfig) this.frontConfigMap.get(appKey);
		if (frontConfig == null) {
			return null;
		}
		return frontConfig.getMappings();
	}

	public List<com.ouc.pampas.Component> findComponents(String appKey) {
		FrontConfig frontConfig = (FrontConfig) this.frontConfigMap.get(appKey);
		if (frontConfig == null) {
			return Lists.newArrayList();
		}
		Map<String, com.ouc.pampas.Component> components = frontConfig.getComponents();
		if ((components == null) || (components.isEmpty())) {
			return Lists.newArrayList();
		}
		return Lists.newArrayList(components.values());
	}

	public com.ouc.pampas.Component findComponent(String appKey, String componentPath) {
		FrontConfig frontConfig = (FrontConfig) this.frontConfigMap.get(appKey);
		if ((frontConfig == null) || (frontConfig.getComponents() == null)) {
			return null;
		}
		return (com.ouc.pampas.Component) frontConfig.getComponents().get(componentPath);
	}

	public List<com.ouc.pampas.Component> findComponentsByCategory(String appKey,
			ComponentCategory category) {
		FrontConfig frontConfig = (FrontConfig) this.frontConfigMap.get(appKey);
		if ((frontConfig == null) || (frontConfig.getComponentCategoryListMap() == null)) {
			return Lists.newArrayList();
		}
		return (List) frontConfig.getComponentCategoryListMap().get(category);
	}

	public Render findRender(String appKey) {
		FrontConfig frontConfig = (FrontConfig) this.frontConfigMap.get(appKey);
		if (frontConfig == null) {
			return null;
		}
		return frontConfig.getRender();
	}

	private synchronized void load() {
		List<App> apps = listAllApp();
		for (App app : apps) {
			loadByApp(app);
		}
	}

	private void loadByApp(App app) {
		try {
			String appKey = app.getKey();
			FrontConfig frontConfig = (FrontConfig) this.configParser.parseConfig(app, FrontConfig.class);
			if (frontConfig != null) {
				this.frontConfigMap.put(appKey, frontConfig);
			}
			BackConfig backConfig = (BackConfig) this.configParser.parseConfig(app, BackConfig.class);
			if (backConfig != null) {
				this.backConfigMap.put(appKey, backConfig);
			}
		} catch (Exception e) {
			log.error("error when load config for app [{}]", app);
			if (this.setting.getMode() == Setting.Mode.IMPLANT) {
				throw new RuntimeException("error when load config", e);
			}
		}
	}

	public void run() {
		try {
			load();
		} catch (Exception e) {
			log.error("error happened when config load heartbeat", e);
		}
	}
}
